#!/usr/bin/env python3

'''
'''

import os
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, Pango
import subprocess
import sys
from Xlib import X, display
from Xlib.ext import xtest




win = None
strcss="""
	button {
		background-color: #eef;
		background-image: -gtk-scaled(url('resource://css/brick.png'), url('resource://css/brick2.png'));
		background-repeat: no-repeat;
		background-position: center;
		/* 测试 */
		/* font-size: {fontsize}px; */
	}
	button:hover {
        background-color: #efe;
		color: #c00;
	}
	button:active {
        background-color: #fee;
		color: #0c0;
	}
	.key_status_down {
        background-color: #fee;
		color: #cc0000;
	}
"""

class KeyButton(Gtk.Button):

    def __init__(self, strLabel1, strLabel2, keycode1=0, keycode2=0, is_status_key=0):
        super(KeyButton,self).__init__(label=strLabel1)
        self.strLabel1 = strLabel1
        self.strLabel2 = strLabel2
        self.keycode1 = keycode1
        self.keycode2 = keycode2
        self.is_status_key = is_status_key

class Keyboard_CHS(Gtk.Box):
    shift_down = False
    cntrl_down = False
    alter_down = False

    class DialogConfig(Gtk.Dialog):
        def __init__(self, parent):
            global strcss

            super().__init__(title="键盘配置", transient_for=parent, flags=0)
            self.add_buttons(
                Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK
            )

            self.set_default_size(150, 100)
            self.set_border_width(10)
            self.rescode = -1

            self.grid = Gtk.Grid()
            box = self.get_content_area()
            box.add(self.grid)

            self.label1 = Gtk.Label(label='v2025.04.28 这个版本没设计有配置项')
            self.grid.attach(self.label1, 0, 0, 2, 1)

            #css加载
            provider = Gtk.CssProvider()
            provider.load_from_data(strcss.encode(encoding='utf-8'))
            self.apply_css(box, provider)

        def apply_css(self, widget, provider):
            Gtk.StyleContext.add_provider(widget.get_style_context(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
            if isinstance(widget, Gtk.Container):
                widget.forall(self.apply_css, provider)

        def start(self):
            self.show_all()
            response = self.run()
            if response == Gtk.ResponseType.CANCEL:
                self.rescode = -1
            self.destroy()
            return self.rescode

    def __init__(self, keyHeight=80, spacing_row=5, spacing_col=5):
        super(Keyboard_CHS,self).__init__(orientation=Gtk.Orientation.VERTICAL, spacing=spacing_row)
        self.keyHeight = keyHeight
        self.spacing_row = spacing_row
        self.spacing_col = spacing_col
        self.set_border_width(5)

        # 初始化X11连接
        self.disp = display.Display()
        self.xtest = xtest

        # 创建按键
        self.createkb()

    def createkb(self):
        
        # 第一行
        label1 = ['ESC', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12']
        label2 = ['ESC', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12']
        kcode1 = [    9,  67 ,  68 ,  69 ,  70 ,  71 ,  72 ,  73 ,  74 ,  75 ,  76 ,   95  ,  96  ]
        kcode2 = [    9,  67 ,  68 ,  69 ,  70 ,  71 ,  72 ,  73 ,  74 ,  75 ,  76 ,   95  ,  96  ]
        rowf = self.create_buttons(label1, label2, kcode1, kcode2)
        self.pack_start(rowf, False, True, 0)

        # 第二行
        label1 = ['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'Backspace']
        label2 = ['~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 'Backspace']
        kcode1 = [ 49,  10,  11,  12,  13,  14,  15,  16 , 17 , 18,  19,  20,  21,  22  ]
        kcode2 = [ 49,  10,  11,  12,  13,  14,  15,  16 , 17 , 18,  19,  20,  21,  22  ]
        rowf = self.create_buttons(label1, label2, kcode1, kcode2)
        self.pack_start(rowf, False, True, 0)

        # 第三行
        label1 = ['Tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\']
        label2 = ['Tab', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|' ]
        kcode1 = [ 23  ,  24,  25,  26,  27,  28,  29,  30 , 31 , 32,  33,  34,  35,  51  ]
        kcode2 = [ 23  ,  24,  25,  26,  27,  28,  29,  30 , 31 , 32,  33,  34,  35,  51  ]
        rowf = self.create_buttons(label1, label2, kcode1, kcode2)
        self.pack_start(rowf, False, True, 0)

        # 第四行
        label1 = ['Caps', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'" , 'Enter']
        label2 = ['Caps', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', "\"", 'Enter']
        kcode1 = [  66  ,  38,  39,  40,  41,  42,  43,  44 , 45 , 46,  47,   48,  36  ]
        kcode2 = [  66  ,  38,  39,  40,  41,  42,  43,  44 , 45 , 46,  47,   48,  36  ]
        rowf = self.create_buttons(label1, label2, kcode1, kcode2)
        self.pack_start(rowf, False, True, 0)

        # 第五行
        label1 = ['Shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'Shift', "↑" ]   # 常规状态下 按钮显示的文本
        label2 = ['Shift', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 'Shift', "↑" ]   # Shift按住时按钮显示的文本
        kcode1 = [  50  ,   52,  53,  54,  55,  56,  57,  58 , 59 , 60,  61,   62   ,  98 ]   # 常规 keycode
        kcode2 = [  50  ,   52,  53,  54,  55,  56,  57,  58 , 59 , 60,  61,   62   ,  98 ]   # Shift按住时的keycode
        status = [  1   ,   0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 ,  0 ,   1    ,  0  ]   # 是不是有状态的按钮
        rowf = self.create_buttons(label1, label2, kcode1, kcode2, status)
        self.pack_start(rowf, False, True, 0)

        # 第六行
        label1 = ['Ctrl', 'Super', 'Alt', 'Space', 'Alt', 'Ctrl', "←" , "↓" , "→" ]
        label2 = ['Ctrl', 'Super', 'Alt', 'Space', 'Alt', 'Ctrl', "←" , "↓" , "→" ]
        kcode1 = [  37  ,   0    ,  64  ,  65    ,  113 ,  109  ,  100,  104,  102]
        kcode2 = [  37  ,   0    ,  64  ,  65    ,  113 ,  109  ,  100,  104,  102]
        status = [  1   ,   0    ,  1   ,  0 ,      1   ,  1    ,  0  ,  0  ,  0  ]
        rowf = self.create_buttons(label1, label2, kcode1, kcode2, status)
        self.pack_start(rowf, False, True, 0)

        # 底部窄长条 
        boxBottom = Gtk.Box() # orientation=Gtk.Orientation.VERTICAL)
        self.pack_start(boxBottom, False, True, 0)

        eventbox = Gtk.EventBox()
        boxBottom.pack_start(eventbox, False, True, 50)
        self.btn_config = Gtk.Label(label="配置")
        eventbox.add(self.btn_config)
        eventbox.connect("button-press-event", self.config, self.btn_config)

        self.status_label = Gtk.Label(label="点击其他窗口后使用本键盘输入")
        boxBottom.pack_start(self.status_label, True, True, 0)

    def config(self, eventbox, event, widget):
        global win
        dialog = self.DialogConfig(win)
        rescode = dialog.start()
        print("DialogConfig rescode: " + str(rescode))

    def create_buttons(self, label1, label2, kcode1, kcode2, status=None):
        row = Gtk.Box(spacing=self.spacing_col)
        i=0
        for btn_label in label1:
            is_status_key = 0
            if status != None:
                is_status_key = status[i]
            btn = KeyButton(label1[i], label2[i], kcode1[i], kcode2[i], is_status_key)
            btn.connect("clicked", self.on_key_pressed)

            # 有重复按键
            # btn.set_name(btn_label)

            if btn_label == "Space":
                btn.set_size_request(200, self.keyHeight)   # 空格键宽度
            else:
                btn.set_size_request(-1,  self.keyHeight)   # 只调整按钮的高, 宽度由系统决定

            # # 调整特殊键的宽度
            # if btn_label in ['Backspace', 'Tab', 'Caps', 'Enter', 'Shift', 'Ctrl', 'Alt', 'Super']:
            #     btn.set_size_request(100, self.keyHeight)
            # elif btn_label.startswith('F'):   # 功能键宽度
            #     btn.set_size_request(60, self.keyHeight)
            # elif btn_label == "Space":        # 空格键宽度
            #     btn.set_size_request(200, self.keyHeight)
            
            row.pack_start(btn, True, True, 0)
            i = i+1
        return row

    def on_key_pressed(self, button):
        """使用Xlib发送按键事件"""
        btn_label = button.get_label()
        b_send_keydown = True
        b_send_keyup   = True

        # 有状态的按钮
        if button.is_status_key != 0:
            if btn_label == "Shift":
                self.shift_down = not self.shift_down
                # self.on_shift_down()
                GLib.idle_add(self.on_shift_down)
                if self.shift_down:
                    b_send_keyup = False
                    button.get_style_context().add_class("key_status_down")
                else:
                    b_send_keydown = False
                    button.get_style_context().remove_class("key_status_down")
            elif btn_label == "Ctrl":
                self.cntrl_down = not self.cntrl_down
                if self.cntrl_down:
                    b_send_keyup = False
                    button.get_style_context().add_class("key_status_down")
                else:
                    b_send_keydown = False
                    button.get_style_context().remove_class("key_status_down")
            elif btn_label == "Alt":
                self.alter_down = not self.alter_down
                if self.alter_down:
                    b_send_keyup = False
                    button.get_style_context().add_class("key_status_down")
                else:
                    b_send_keydown = False
                    button.get_style_context().remove_class("key_status_down")

        try:
            # # 获取键码
            # keysym = Gdk.unicode_to_keyval(ord(char))
            # keycode = self.disp.keysym_to_keycode(keysym)

            keycode = button.keycode1
            if b_send_keydown:
                self.xtest.fake_input(self.disp, X.KeyPress,   keycode)
            if b_send_keyup:
                self.xtest.fake_input(self.disp, X.KeyRelease, keycode)
            self.disp.sync()

        except Exception as e:
            self.status_label.set_text(f"输入错误: {str(e)}")

    def on_shift_down(self):
        # print("正在更新按钮上的文字")
        row_childs = self.get_children()
        for row_box in row_childs:
            if not isinstance(row_box, Gtk.Box):
                continue
            btn_childs = row_box.get_children()
            for btn in btn_childs:
                if not isinstance(btn, KeyButton):
                    continue
                newlabel=btn.strLabel1
                if self.shift_down:
                    newlabel=btn.strLabel2
                # print("newlabel: " + newlabel)
                btn.set_label(newlabel)
        self.queue_draw()
        self.disp.sync()


class MainForm(Gtk.Window):
    css_provider = None
    fontsize="28"
    keyHeight=100

    def __init__(self):
        global win
        win = self

        super(MainForm,self).__init__(title="正卓屏幕键盘")

        app_icon_path="/exbin/tools/zzswmgr/appicons/zzvkb.jfif"
        if os.path.exists(app_icon_path):
            self.set_icon_from_file(app_icon_path)

        self.xtest = xtest
        self.disp = display.Display()
        # print(self.disp)
        # print(self.get_display().get_xdisplay())

        self.popup_ext_keys()

        # 创建键盘布局
        self.kb = Keyboard_CHS(keyHeight=100)
        self.add(self.kb)
        self.maxh = self.kb.get_allocated_height()

        # 窗口设置
        self.set_keep_above(True)
        # self.set_decorated(False)     # 隐藏标题栏
        # self.set_resizable(False)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_accept_focus(False)  # 防止键盘窗口抢焦点
        self.set_default_size(Gdk.Display.get_default().get_monitor(0).get_geometry().width, self.maxh)  # 增大窗口以适应功能键
        self.winsize_limit()
        self.connect("window-state-event", self.on_window_state_event)

        # 套用css样式
        self.apply_css(self)


    def winsize_limit(self):
        # # 设置 geometry hints（限制窗口最大和最小的宽高）
        # geometry_hints = Gdk.Geometry()
        # geometry_hints.min_width = 100
        # geometry_hints.max_width = Gdk.Display.get_default().get_monitor(0).get_geometry().width
        # geometry_hints.min_height = self.maxh
        # geometry_hints.max_height = self.maxh
        # self.set_geometry_hints(self, geometry_hints, Gdk.WindowHints.MIN_SIZE | Gdk.WindowHints.MAX_SIZE)

        self.resize(Gdk.Display.get_default().get_monitor(0).get_geometry().width, self.maxh)
        # self.unmaximize()
        # self.queue_draw()

    def on_window_state_event(self, window, event):
        """监听窗口状态变化，拦截最大化事件"""
        if event.new_window_state & Gdk.WindowState.MAXIMIZED:
            self.winsize_limit()
            return True  # 阻止默认最大化行为
        return False

    def apply_css(self, widget):
        global strcss

        if widget == None:
            return
        if self.css_provider == None:
            tmpcss = strcss.replace("/* font-size: {fontsize}px; */", "font-size: " + self.fontsize + "px;")
            self.css_provider = Gtk.CssProvider()
            self.css_provider.load_from_data(tmpcss.encode(encoding='utf-8'))

        # print("正在套用 css 到：" + str(widget))
        Gtk.StyleContext.add_provider(widget.get_style_context(), self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

        # 套用到所有子控件
        if isinstance(widget, Gtk.Container):
            widget.forall(self.apply_css)

    def apply_css_for_all_child_widgets(self, widget):
        """递归遍历 container 中的所有子控件，并套用css"""
        if widget == None:
            return

        # print("套用 css 到：" + str(widget))
        Gtk.StyleContext.add_provider(widget.get_style_context(), self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

        if not isinstance(widget, Gtk.Container):
            return

        # 遍历 container 的直接子控件
        for child in widget.get_children():
            self.apply_css_for_all_child_widgets(child)

    def popup_ext_keys(self):
        # try:
            self.xtest.fake_input(self.disp, X.KeyRelease, 50) # L_shift
            self.xtest.fake_input(self.disp, X.KeyRelease, 37) # L_ctrl
            self.xtest.fake_input(self.disp, X.KeyRelease, 64) # L_alt

            self.xtest.fake_input(self.disp, X.KeyRelease, 62) # R_shift
            self.xtest.fake_input(self.disp, X.KeyRelease,109) # R_ctrl
            self.xtest.fake_input(self.disp, X.KeyRelease,113) # R_alt
            self.disp.sync()
        # except Exception as e:
        #     print("错误：" + str(e))



def main():

    win = MainForm()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()

if __name__ == "__main__":
    main()

